package com.mjlf.mjatis.mapper;

import com.mjlf.mjatis.config.Configuration;
import com.mjlf.mjatis.core.SqlSession;
import com.mjlf.mjatis.exception.MapperException;
import lombok.Data;
import lombok.extern.java.Log;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @ClassName MapperProxy
 * @Author mjlft
 * @Date 2020/6/9 23:25
 * @Version 1.0
 * @Description Mapper执行代理器
 * <p>
 * incoke执行的逻辑， 根据method找到具体MapperMethod, 然后通过MapperMethod执行excute()方法，
 * 这个方法才是执行正在sql的地方
 */
@Data
@Log
public class MapperProxy implements InvocationHandler, Serializable {
    private static final long serialVersionUID = -6424540398559729838L;
    private static final int ALLOWED_MODES = MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
            | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC;
    private Configuration configuration;
    private Map<Method, MapperMethod> cacheMethod = new ConcurrentHashMap<>();
    private static final Method privateLookupInMethod;
    private static final Constructor<MethodHandles.Lookup> lookupConstructor;

    public MapperProxy(Configuration configuration) {
        this.configuration = configuration;
    }

    public MapperProxy() {
    }

    static {
        Method privateLookupIn;
        try {
            privateLookupIn = MethodHandles.class.getMethod("privateLookupIn", Class.class, MethodHandles.Lookup.class);
        } catch (NoSuchMethodException e) {
            privateLookupIn = null;
        }
        privateLookupInMethod = privateLookupIn;

        Constructor<MethodHandles.Lookup> lookup = null;
        if (privateLookupInMethod == null) {
            // JDK 1.8
            try {
                lookup = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
                lookup.setAccessible(true);
            } catch (NoSuchMethodException e) {
                throw new IllegalStateException(
                        "There is neither 'privateLookupIn(Class, Lookup)' nor 'Lookup(Class, int)' method in java.lang.invoke.MethodHandles.",
                        e);
            } catch (Throwable t) {
                lookup = null;
            }
        }
        lookupConstructor = lookup;
    }


    //具体执行的逻辑
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            } else if (method.isDefault()) {
                if (privateLookupInMethod == null) {
                    return invokeDefaultMethodJava8(proxy, method, args);
                } else {
                    return invokeDefaultMethodJava9(proxy, method, args);
                }
            }
        } catch (Throwable t) {
            throw new MapperException("Mapper调用异常");
        }
        MapperMethod mapperMethod = this.cacheMethod.get(method);
        return mapperMethod.execute(this.configuration, args, method.getParameters());
    }

    /**
     * 解析配置中Mapper, 找到对应的method, 并生成MapperMethod
     */
    public void parseMapperMethod(Class clazz) {
        String namespace = clazz.getName();
        if (configuration.getMapperStatements().containsKey(namespace)) {
            MapperStatement mapperStatement = configuration.getMapperStatements().get(namespace);
            List<Method> methodList = Arrays.asList(clazz.getDeclaredMethods());
            mapperStatement.getMapperMethods().stream().forEach(mapperMethod -> {
                for (Method method : methodList) {
                    if (method.getName().equals(mapperMethod.getId())) {
                        cacheMethod.put(method, mapperMethod);
                        break;
                    }
                }
            });
        } else {
            log.info("Mapper 生成失败， 不存在" + namespace + "配置");
            throw new MapperException("Mapper 生成失败， 不存在" + namespace + "配置");
        }
    }

    private Object invokeDefaultMethodJava9(Object proxy, Method method, Object[] args)
            throws Throwable {
        final Class<?> declaringClass = method.getDeclaringClass();
        return ((MethodHandles.Lookup) privateLookupInMethod.invoke(null, declaringClass, MethodHandles.lookup()))
                .findSpecial(declaringClass, method.getName(),
                        MethodType.methodType(method.getReturnType(), method.getParameterTypes()), declaringClass)
                .bindTo(proxy).invokeWithArguments(args);
    }

    private Object invokeDefaultMethodJava8(Object proxy, Method method, Object[] args)
            throws Throwable {
        final Class<?> declaringClass = method.getDeclaringClass();
        return lookupConstructor.newInstance(declaringClass, ALLOWED_MODES).unreflectSpecial(method, declaringClass)
                .bindTo(proxy).invokeWithArguments(args);
    }
}
